package com.me.web.api.idaas;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.me.common.constant.CacheConstants;
import com.me.common.constant.Constants;
import com.me.common.core.controller.BaseController;
import com.me.common.core.domain.AjaxResult;
import com.me.common.core.domain.entity.SysUser;
import com.me.common.core.redis.RedisCache;
import com.me.common.utils.SecurityUtils;
import com.me.common.utils.StringUtils;
import com.me.framework.web.service.SysLoginService;
import com.me.web.api.idaas.domain.IdaasToken;
import com.me.web.api.idaas.domain.IdaasUser;
import com.me.web.system.domain.SysGroup;
import com.me.web.system.domain.SysGroupRole;
import com.me.web.system.service.ISysUserService;
import com.me.web.system.service.SysGroupRoleService;
import com.me.web.system.service.SysGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * OAuth2授权认证
 */
@RestController
@RequestMapping("/IdaasAuthorize")
public class IdaasController extends BaseController {


    // 应用编码
    @Value("${idaas.clientId}")
    private String clientId;
    // 应用秘钥
    @Value("${idaas.clientSecret}")
    private String clientSecret;
    // idaas根地址
    @Value("${idaas.baseUrl}")
    private String baseUrl;
    //本系统的回调地址
    @Value("${idaas.callbackUrl}")
    private String callbackUrl;

    @Resource
    private ISysUserService sysUserService;
    @Resource
    private SysGroupService sysGroupService;
    @Resource
    private SysGroupRoleService sysGroupRoleService;
    @Resource
    private SysLoginService sysLoginService;
    @Autowired
    private RedisCache redisCache;

    /**
     * 通过GET请求获取授权URL
     * <p>
     * 本方法旨在提供一个接口，用于获取IDaaS（Identity as a Service）的授权URL。
     * 客户端可以通过调用这个接口，获取用于用户授权的URL，然后引导用户访问该URL进行授权流程。
     *
     * @return AjaxResult 包含授权URL的成功响应
     */
    @GetMapping(value = "/getAuthorizeUrl")
    public AjaxResult getAuthorizeUrl() {
        // 调用IdaasUtils中的getAuthUrl方法获取授权URL，并将结果包装在AjaxResult对象中返回
        String authUrl = baseUrl + "/authz/oauth/v20/authorize?client_id=" + clientId + "&redirect_uri="
                + callbackUrl + "&response_type=code&approval_prompt=auto";
        return success(authUrl);
    }

    /**
     * 使用IDAAS认证回调方法
     *
     * @param code IDAAS认证返回的授权码
     * @return 登录成功后返回的令牌
     */
    @GetMapping("/callback/{code}")
    @Transactional
    public AjaxResult callback(@PathVariable("code") String code) {
        // 通过IDAAS授权码获取IDAAS令牌信息
        IdaasToken idaasToken = this.getIdaasToken(code);
        // 通过IDAAS令牌获取用户信息
        IdaasUser userInfo = this.getUserInfo(idaasToken.getAccessToken());

        // 检查用户信息中用户名是否为空，为空则表示IDAAS认证失败
        if (StringUtils.isEmpty(userInfo.getUsername())) {
            return AjaxResult.error("登录IDAAS失败，请返回重试");
        }

        // 根据用户手机号或用户名查询系统中是否存在该用户
        SysUser sysUser = null;
        if (StringUtils.isNotEmpty(userInfo.getMobile())) {
            // 根据手机号查询用户
            List<SysUser> userPhoneList = sysUserService.list(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, userInfo.getMobile()));
            // 如果存在多个相同手机号用户，返回错误信息
            if (userPhoneList.size() > 1) {
                return AjaxResult.error("存在重复手机号请联系管理员");
            } else if (userPhoneList.size() == 1) {
                sysUser = userPhoneList.get(0);
            }
        } else {
            // 根据用户名查询用户
            List<SysUser> userNameList = sysUserService.list(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, userInfo.getUsername()));
            // 如果存在多个相同用户名用户，返回错误信息
            if (userNameList.size() > 1) {
                return AjaxResult.error("存在重复用户名请联系管理员");
            } else if (userNameList.size() == 1) {
                sysUser = userNameList.get(0);
            }
        }

        // 如果系统中不存在该用户，则创建新用户
        if (StringUtils.isNull(sysUser) || StringUtils.isNull(sysUser.getUserId())) {
            // 初始化新用户信息并设置默认值
            SysUser insertUser = new SysUser();
            insertUser.setDeptId(100L);
            insertUser.setNickName(userInfo.getDisplayName());
            insertUser.setUserName(userInfo.getUsername());
            insertUser.setEmail(userInfo.getEmail());
            insertUser.setPhonenumber(userInfo.getMobile());
            // 为新用户生成随机密码
            insertUser.setPassword(SecurityUtils.encryptPassword(RandomUtil.randomString(12)));
            insertUser.setStatus("0");
            insertUser.setDelFlag("0");
            // 根据用户所属的用户组设置用户角色
            if (userInfo.getGroupCodes().size() > 0) {
                List<SysGroup> sysGroups = sysGroupService.list(new LambdaQueryWrapper<SysGroup>()
                        .eq(SysGroup::getStatus, "0")
                        .in(SysGroup::getGroupCode, userInfo.getGroupCodes()));
                if (sysGroups.size() > 0) {
                    List<SysGroupRole> sysGroupRoles = sysGroupRoleService.list(new LambdaQueryWrapper<SysGroupRole>()
                            .in(SysGroupRole::getGroupId, sysGroups.stream().map(SysGroup::getId).collect(Collectors.toList())));
                    if (sysGroupRoles.size() > 0) {
                        List<Long> groupRoleIds = sysGroupRoles.stream().map(SysGroupRole::getRoleId).collect(Collectors.toList());
                        insertUser.setRoleIds(groupRoleIds.stream().toArray(Long[]::new));
                    }
                }
            }
            // 如果用户没有被分配任何角色，返回错误信息
            if (StringUtils.isNull(insertUser.getRoleIds()) || insertUser.getRoleIds().length == 0) {
                return error("用户组设置错误，请联系管理员");
            }
            // 设置用户密码更新时间为未来，表示不需要立即更新密码
            insertUser.setUpdatePwdTime(DateUtil.parseDateTime("2099-01-01 00:00:00"));
            insertUser.setCreateBy("IDAAS");
            insertUser.setCreateTime(new Date());
            sysUserService.insertUser(insertUser);
            sysUser = insertUser;
        }
        // 登录成功，返回用户令牌
        AjaxResult ajax = AjaxResult.success();
        String token = sysLoginService.loginByUserName(userInfo.getUsername());
        ajax.put(Constants.TOKEN, token);
        //放入应用信息到redis中
        saveIdaasUserApp(sysUser, userInfo);
        return ajax;
    }


    /**
     * 保存IDaaS用户应用信息。
     * 将用户的应用信息存储到Redis缓存中，以列表的形式，键由用户ID和常量字符串拼接而成。
     * 此方法主要用于缓存用户的应用权限信息，以便快速访问和查询。
     *
     * @param sysUser  系统用户对象，包含用户的ID信息。
     * @param userInfo IDaaS用户信息对象，包含用户的应用权限信息。
     */
    private void saveIdaasUserApp(SysUser sysUser, IdaasUser userInfo) {
        // 将用户的应用信息列表缓存到Redis中，键为IDAAS_APP_LIST_KEY常量与用户ID的拼接字符串
        redisCache.setCacheList(CacheConstants.IDAAS_APP_LIST_KEY + sysUser.getUserId(), userInfo.getUserApps());
    }


    /**
     * 通过授权码获取IDaaS的访问令牌。
     *
     * 本方法使用授权码模式，向IDaaS服务端发送请求，以换取访问令牌（Access Token）和刷新令牌（Refresh Token）。
     * 这是OAuth 2.0授权框架中的一种模式，用于客户端通过用户代理（通常是浏览器）获取访问令牌。
     *
     * @param code 用户授权后返回的授权码。这是客户端在用户授权过程中获取到的，用于换取令牌。
     * @return 返回IDaaSTokenVo对象，其中包含了访问令牌、刷新令牌等相关信息。
     */
    public IdaasToken getIdaasToken(String code) {
        // 构建请求参数映射，包含客户端ID、客户端密钥、授权类型等必要信息。
        Map map = new HashMap();
        map.put("client_id", clientId);
        map.put("client_secret", clientSecret);
        map.put("grant_type", "authorization_code");
        map.put("redirect_uri", callbackUrl);
        map.put("code", code);
        // 向IDaaS服务端发送GET请求，获取令牌信息。
        String tokenStr = HttpUtil.get(baseUrl + "/authz/oauth/v20/token", map);
        // 将返回的令牌信息字符串，转换为IDaaSTokenVo对象。
        IdaasToken token = JSONUtil.toBean(tokenStr, IdaasToken.class);
        return token;
    }


    /**
     * 通过访问令牌获取IDaaS用户信息。
     *
     * 本函数使用提供的访问令牌（access_token）向IDaaS服务发送请求，以获取当前授权用户的详细信息。
     * 请求的URL由基础URL和特定的API路径组成，请求参数包括访问令牌。
     * 接收到的响应被解析为JSON格式，然后映射到IdaasUserVo对象，该对象代表了用户的详细信息。
     *
     * @param access_token 用于授权的访问令牌，通过OAuth流程获取。
     * @return IdaasUserVo对象，包含从IDaaS服务获取的用户信息。
     */
    public IdaasUser getUserInfo(String access_token) {
        // 构建请求参数映射，包含访问令牌
        Map map = new HashMap();
        map.put("access_token", access_token);
        // 发送GET请求到IDaaS服务，获取用户信息，并将响应作为字符串返回
        String userInfoStr = HttpUtil.get(baseUrl + "/api/oauth/v20/me", map);
        // 将响应字符串解析为IdaasUserVo对象，用于进一步处理或返回
        IdaasUser user = JSONUtil.toBean(userInfoStr, IdaasUser.class);
        // 返回解析后的用户信息对象
        return user;
    }


}
